package com.example.demo.generator.scaffold.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.common.PageResult;
import com.example.demo.generator.scaffold.dto.ScaffoldGenerationRequest;
import com.example.demo.generator.scaffold.dto.log.ScaffoldLogListRequest;
import com.example.demo.generator.scaffold.dto.log.ScaffoldLogRecord;
import com.example.demo.generator.scaffold.dto.stats.ScaffoldStatsDimension;
import com.example.demo.generator.scaffold.dto.stats.ScaffoldStatsQueryRequest;
import com.example.demo.generator.scaffold.dto.stats.ScaffoldStatsResponse;
import com.example.demo.generator.scaffold.dto.stats.ScaffoldStatsResult;
import com.example.demo.generator.scaffold.entity.ScaffoldGenerationLog;
import com.example.demo.generator.scaffold.mapper.ScaffoldGenerationLogMapper;
import com.example.demo.generator.scaffold.service.ScaffoldGenerationLogService;
import com.example.demo.generator.scaffold.util.ZipPackagingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;

/**
 * 脚手架生成记录服务实现。
 */
@Slf4j
@Service
@ConditionalOnProperty(prefix = "feature.database", name = "enabled", havingValue = "true")
public class ScaffoldGenerationLogServiceImpl extends ServiceImpl<ScaffoldGenerationLogMapper, ScaffoldGenerationLog>
        implements ScaffoldGenerationLogService {

    @Override
    public void recordGeneration(ScaffoldGenerationRequest request, ZipPackagingService.ZipResult zipResult, String clientIp, Long userId) {
        try {
            ScaffoldGenerationLog log = new ScaffoldGenerationLog();
            log.setUserId(userId);
            log.setModuleName(request.getModuleName());
            log.setModuleBasePath(request.getModuleBasePath());
            log.setBasePackage(request.getBasePackage());
            boolean enableBlockchain = request.getScaffoldOptions() != null && request.getScaffoldOptions().isEnableBlockchain();
            log.setEnableBlockchain(enableBlockchain);
            int chainFieldsCount = request.getScaffoldOptions() != null && request.getScaffoldOptions().getChainFields() != null
                    ? request.getScaffoldOptions().getChainFields().size()
                    : 0;
            log.setChainFieldsCount(chainFieldsCount);
            log.setZipFileName(zipResult.getZipFile().getFileName().toString());
            log.setDownloadUrl(zipResult.getDownloadUrl());
            log.setRequestIp(clientIp);
            log.setRequestPayload(JSONUtil.toJsonStr(request));
            log.setCreatedAt(LocalDateTime.now());
            save(log);
        } catch (Exception ex) {
            log.warn("记录脚手架 ZIP 生成日志失败: {}", ex.getMessage());
            log.debug("recordGeneration error", ex);
        }
    }

    @Override
    public ScaffoldStatsResponse queryStats(ScaffoldStatsQueryRequest request) {
        LambdaQueryWrapper<ScaffoldGenerationLog> wrapper = Wrappers.lambdaQuery();
        if (StringUtils.hasText(request.getModuleName())) {
            wrapper.eq(ScaffoldGenerationLog::getModuleName, request.getModuleName());
        }
        if (StringUtils.hasText(request.getBasePackage())) {
            wrapper.eq(ScaffoldGenerationLog::getBasePackage, request.getBasePackage());
        }
        if (StringUtils.hasText(request.getModuleBasePath())) {
            wrapper.eq(ScaffoldGenerationLog::getModuleBasePath, request.getModuleBasePath());
        }
        if (request.getUserId() != null) {
            wrapper.eq(ScaffoldGenerationLog::getUserId, request.getUserId());
        }
        if (request.getEnableBlockchain() != null) {
            wrapper.eq(ScaffoldGenerationLog::getEnableBlockchain, request.getEnableBlockchain());
        }
        if (request.getMinChainFields() != null) {
            wrapper.ge(ScaffoldGenerationLog::getChainFieldsCount, request.getMinChainFields());
        }
        if (request.getMaxChainFields() != null) {
            wrapper.le(ScaffoldGenerationLog::getChainFieldsCount, request.getMaxChainFields());
        }
        if (request.getStartTime() != null) {
            wrapper.ge(ScaffoldGenerationLog::getCreatedAt, request.getStartTime());
        }
        if (request.getEndTime() != null) {
            wrapper.le(ScaffoldGenerationLog::getCreatedAt, request.getEndTime());
        }
        List<ScaffoldGenerationLog> logs = list(wrapper);

        ScaffoldStatsResponse response = new ScaffoldStatsResponse();
        response.setTotal(logs.size());

        List<ScaffoldStatsDimension> dimensions = request.getGroupBy();
        if (CollectionUtil.isEmpty(dimensions)) {
            ScaffoldStatsResult result = new ScaffoldStatsResult();
            result.setCount(logs.size());
            response.setItems(List.of(result));
            return response;
        }

        Map<String, Aggregation> aggregationMap = new LinkedHashMap<>();
        for (ScaffoldGenerationLog logEntry : logs) {
            Map<String, Object> dimensionMap = buildDimensionMap(dimensions, logEntry);
            String key = buildAggregationKey(dimensionMap);
            Aggregation aggregation = aggregationMap.computeIfAbsent(key, k -> new Aggregation(dimensionMap));
            aggregation.counter.increment();
        }

        List<ScaffoldStatsResult> results = new ArrayList<>();
        aggregationMap.values().forEach(aggregation -> {
            ScaffoldStatsResult item = new ScaffoldStatsResult();
            item.setDimensions(new LinkedHashMap<>(aggregation.dimensions));
            item.setCount(aggregation.counter.longValue());
            results.add(item);
        });
        response.setItems(results);
        return response;
    }

    @Override
    public PageResult<ScaffoldLogRecord> listLogs(ScaffoldLogListRequest request) {
        long current = request.currentOrDefault();
        long pageSize = request.pageSizeOrDefault();
        Page<ScaffoldGenerationLog> page = new Page<>(current, pageSize);

        LambdaQueryWrapper<ScaffoldGenerationLog> wrapper = Wrappers.lambdaQuery();
        String keyword = request.getKeyword();
        if (StringUtils.hasText(keyword)) {
            wrapper.and(query -> query.like(ScaffoldGenerationLog::getModuleName, keyword)
                    .or().like(ScaffoldGenerationLog::getBasePackage, keyword)
                    .or().like(ScaffoldGenerationLog::getModuleBasePath, keyword));
        }
        wrapper.orderByDesc(ScaffoldGenerationLog::getCreatedAt);

        Page<ScaffoldGenerationLog> result = page(page, wrapper);
        List<ScaffoldLogRecord> records = result.getRecords().stream()
                .map(this::convertLogRecord)
                .collect(Collectors.toList());

        return PageResult.<ScaffoldLogRecord>builder()
                .records(records)
                .total(result.getTotal())
                .current(result.getCurrent())
                .pageSize(result.getSize())
                .build();
    }

    private ScaffoldLogRecord convertLogRecord(ScaffoldGenerationLog logEntry) {
        ScaffoldLogRecord record = new ScaffoldLogRecord();
        record.setId(logEntry.getId());
        record.setModuleName(logEntry.getModuleName());
        record.setBasePackage(logEntry.getBasePackage());
        record.setModuleBasePath(logEntry.getModuleBasePath());
        record.setEnableBlockchain(Boolean.TRUE.equals(logEntry.getEnableBlockchain()));
        record.setZipFileName(logEntry.getZipFileName());
        record.setDownloadUrl(logEntry.getDownloadUrl());
        record.setRequestIp(logEntry.getRequestIp());
        record.setCreatedAt(logEntry.getCreatedAt());
        record.setGenerateType("ZIP_EXPORT");

        String databaseType = null;
        try {
            if (StringUtils.hasText(logEntry.getRequestPayload())) {
                ScaffoldGenerationRequest request = JSONUtil.toBean(logEntry.getRequestPayload(), ScaffoldGenerationRequest.class);
                if (request != null && request.getScaffoldOptions() != null && request.getScaffoldOptions().getDatabaseType() != null) {
                    databaseType = request.getScaffoldOptions().getDatabaseType().name();
                }
            }
        } catch (Exception ex) {
            log.warn("解析脚手架日志请求失败: {}", ex.getMessage());
            log.debug("convertLogRecord parse error", ex);
        }
        record.setDatabaseType(databaseType);
        return record;
    }

    private Map<String, Object> buildDimensionMap(List<ScaffoldStatsDimension> dimensions, ScaffoldGenerationLog log) {
        Map<String, Object> dimensionMap = new LinkedHashMap<>();
        for (ScaffoldStatsDimension dimension : dimensions) {
            switch (dimension) {
                case MODULE_NAME:
                    dimensionMap.put("moduleName", log.getModuleName());
                    break;
                case BASE_PACKAGE:
                    dimensionMap.put("basePackage", log.getBasePackage());
                    break;
                case MODULE_BASE_PATH:
                    dimensionMap.put("moduleBasePath", log.getModuleBasePath());
                    break;
                case USER:
                    dimensionMap.put("userId", log.getUserId());
                    break;
                case BLOCKCHAIN_ENABLED:
                    dimensionMap.put("enableBlockchain", Boolean.TRUE.equals(log.getEnableBlockchain()));
                    break;
                case CHAIN_FIELD_BUCKET:
                    dimensionMap.put("chainFieldBucket", toChainFieldBucket(log.getChainFieldsCount()));
                    break;
                case DATE:
                    LocalDateTime createdAt = log.getCreatedAt();
                    LocalDate date = createdAt != null ? createdAt.toLocalDate() : null;
                    dimensionMap.put("date", date);
                    break;
                default:
                    break;
            }
        }
        return dimensionMap;
    }

    private String buildAggregationKey(Map<String, Object> dimensionMap) {
        StringBuilder builder = new StringBuilder();
        dimensionMap.forEach((key, value) -> {
            if (builder.length() > 0) {
                builder.append("|");
            }
            builder.append(key).append("=").append(value);
        });
        return builder.toString();
    }

    private String toChainFieldBucket(Integer count) {
        int value = count != null ? count : 0;
        if (value == 0) {
            return "0";
        }
        if (value <= 3) {
            return "1-3";
        }
        return "4+";
    }

    private static class Aggregation {
        private final Map<String, Object> dimensions;
        private final LongAdder counter = new LongAdder();

        private Aggregation(Map<String, Object> dimensions) {
            this.dimensions = new LinkedHashMap<>(dimensions);
        }
    }
}


